/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */

package org.griphyn.vdl.util;

import org.griphyn.vdl.util.*;
import java.util.*;
import java.io.*;

/**
 * This class allows access to a set of files. With the constructor, the
 * basename of the fileset is specified. From this file, a set of 10
 * files can be accessed and constructed, with suffices ".0" to ".9". A
 * cursor file, suffix ".nr", points to the currently active file in the
 * ring.<p>
 *
 * In read mode, the cursor file gets locked temporarily while the stream
 * is opened. If no cursor file exists, it is assumed that the basename
 * is also the filename of the database. A lock file for the opened database
 * will be created.<p>
 *
 * In write mode, the cursor file gets locked until the writer is being
 * closed again. Thus, parallel access by other writers or readers are
 * prohibited. The cursor is advanced at stream close. The database stream
 * points to the next file beyong the cursor. If no cursor file existed, it
 * will point to suffix ".0".<p>
 *
 * All access to the files must go through the respective open and close
 * methods provided by this class in order to guarantee proper locking!
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see java.io.File
 * @see LockHelper
 */
abstract public class FileHelper 
{
  /**
   * base name of the fileset to access. 
   */
  protected String m_database;

  /**
   * description of the cursor file for the given basename set.
   */
  protected File m_number;

  /**
   * Primary ctor: obtain access to a database cycle via basename.
   * @param basename is the name of the database without digit suffix.
   */
  public FileHelper( String basename )
  {
    this.m_database = basename;
    this.m_number = new File( basename + ".nr" );
  }

  /**
   * Reads the cursor file and obtains the current cycle position.
   * The contract requires you to hold the lock for the cursor file.
   *
   * @return the current cycle position, or -1 to indicate failure.
   */
  protected int readCount()
  {
    int result = -1;
    try {
      LineNumberReader lnr =
	new LineNumberReader( new FileReader(this.m_number) );
      result = Integer.parseInt(lnr.readLine());
      lnr.close();
    } catch ( IOException ioe ) {
      Logging.instance().log( "lock", 2, "unable to process " + 
			      this.m_number.getPath() + ": " +
			      ioe.getMessage() );
      result = -1; // make extra sure
    } catch ( NumberFormatException nfe ) {
      result = -1; // make extra sure
    }
    return result;
  }

  /**
   * Updates the cursor file with a new cycle position. The contract
   * requires you to hold the lock for the cursor file.
   *
   * @param number is the new cursor position.
   * @return true, if the file was updated all right, false,
   * if an error occured during update.
   */
  protected boolean writeCount( int number )
  {
    boolean result = false;
    try {
      FileWriter fw = new FileWriter(this.m_number);
      fw.write( Integer.toString(number) + 
		System.getProperty( "line.separator", "\r\n" ) );
      fw.close();
      result = true;
    }
    catch ( IOException ioe ) {
      Logging.instance().log( "lock", 2, "unable to update " + 
			      this.m_number.getPath() + ": " +
			      ioe.getMessage() );
      result = false;
    }
    return result;
  }

  /**
   * Opens a reader for the basename, adjusting the database cycles.
   * The access can be shared with other simultaneous readers.
   * @return a reader opened to the basename, or null for failure.
   */
  abstract public File openReader();

  /**
   * Closes a previously obtained reader, and releases internal
   * locking resources. Only if the reader was found in the internal
   * state, any closing of the stream will be attempted.
   *
   * @param r is the reader that was created by {@link #openReader()}.
   * @return true, if unlocking went smoothly, or false in the presence
   * of an error. The only error that can happen it to use a File
   * instance which was not returned by this instance.
   */
  abstract public boolean closeReader( File r );

  /**
   * Opens a writer for the basename, adjusting the database cycles
   * The access is exclusive, and cannot be shared with readers nor
   * writers.
   *
   * @return a writer opened for the basename, or null for failure.
   */
  abstract public File openWriter();

  /**
   * Closes a previously obtained writer, and releases internal
   * locking resources. Error conditions can be either a missing
   * instance that passed, or the inability to update the cursor file.
   *
   * @param w is the instance that was returned by {@link #openWriter()}.
   * @return true, if the closing went smoothly, false in the presence
   * of an error.
   */
  abstract public boolean closeWriter( File w );
}
